﻿// (c) Copyright Fernando Cerqueira.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections;

// ReSharper disable CheckNamespace
namespace DevPhone.Controls
// ReSharper restore CheckNamespace
{

    [TemplateVisualState(Name = "Collapsed", GroupName = "ViewStates")]
    [TemplateVisualState(Name = "Expanded", GroupName = "ViewStates")]
    [TemplateVisualState(Name = "Disabled", GroupName = "ViewStates")]
    [TemplatePart(Name = "Header", Type = typeof(ContentControl))]
    [TemplatePart(Name = "SeletedContent", Type = typeof(ContentControl))]
    [TemplatePart(Name = "AreaEllipse", Type = typeof(Border))]
    [TemplatePart(Name = "HostItemPresenter", Type = typeof(Border))]
    public class DropDownControl : ItemsControl
    {

        #region events

        public event EventHandler<DropDownOpenChangeEventArgs> ChangeExpanded;
        public event EventHandler<SelectionChangedEventArgs> SelectionChange;

        public void OnChangeExpanded(DropDownOpenChangeEventArgs e)
        {
            var handler = ChangeExpanded;
            if (handler != null) handler(this, e);
        }

        public void OnSelectionChange(SelectionChangedEventArgs e)
        {
            var handler = SelectionChange;
            if (handler != null) handler(this, e);
        }

        #endregion

        #region private fields
        
        //flag loaded contrlol
        private bool _isLoadedTemplate;
        private Grid _tapElementExpander;
        private ContentControl _seletedContentElement;
        private Ellipse _ellipse;
        private Path _arrowExpander;
        private static Brush _defaultEllipseBrush;
        private static Brush _defaultEllipseArrow;
        private static Brush _defaultBorder;
        private static Thickness _defaultBorderThickness;
        private static Brush _defaultDisabled;
        private static Brush _defaulControlBackground;
        private static Brush _defaulControlForeground;
        private double _heightContent;
        private bool _isExpanded;
        private ScrollViewer _scrollViewer;
        private Border _hostPresenter;
        private double? _heightItem;
        private Border _areaEllipse;


        #endregion

        #region Dependency Properties

        public static readonly DependencyProperty HeaderProperty =
            DependencyProperty.Register("Header", typeof(object), typeof(DropDownControl), new PropertyMetadata(null));

        public object Header
        {
            get { return GetValue(HeaderProperty); }
            set { SetValue(HeaderProperty, value); }
        }

        public static readonly DependencyProperty HeaderTemplateProperty =
            DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(DropDownControl), new PropertyMetadata(null));

        public DataTemplate HeaderTemplate
        {
            get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
            set { SetValue(HeaderTemplateProperty, value); }
        }


        public static readonly DependencyProperty ContainerAlignmentProperty =
            DependencyProperty.Register("ContainerAlignment", typeof(DropDownContainerAlignment), typeof(DropDownControl), new PropertyMetadata(DropDownContainerAlignment.Right, ContainerAlignmentChangedCallback));

        private static void ContainerAlignmentChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (DropDownControl)dependencyObject;
            if (instance._isLoadedTemplate)
            {
                switch (instance.ContainerAlignment)
                {
                    case DropDownContainerAlignment.Left:
                        instance._hostPresenter.HorizontalAlignment = HorizontalAlignment.Left;
                        break;
                    case DropDownContainerAlignment.Right:
                        instance._hostPresenter.HorizontalAlignment = HorizontalAlignment.Right;
                        break;
                    case DropDownContainerAlignment.Center:
                        instance._hostPresenter.HorizontalAlignment = HorizontalAlignment.Center;
                        break;
                }
            }
        }

        public DropDownContainerAlignment ContainerAlignment
        {
            get { return (DropDownContainerAlignment)GetValue(ContainerAlignmentProperty); }
            set
            {
                SetValue(ContainerAlignmentProperty, value);
            }
        }
        
        public static readonly DependencyProperty SelectedTemplateProperty =
            DependencyProperty.Register("SelectedTemplate", typeof(DataTemplate), typeof(DropDownControl), new PropertyMetadata(null, null));

        public DataTemplate SelectedTemplate
        {
            get { return (DataTemplate)GetValue(SelectedTemplateProperty); }
            set
            {
                SetValue(SelectedTemplateProperty, value);
            }
        }

        public static readonly DependencyProperty ItemCountThresholdProperty =
            DependencyProperty.Register("ItemCountThreshold", typeof(int), typeof(DropDownControl), new PropertyMetadata(3, null));

        public int ItemCountThreshold
        {
            get { return (int)GetValue(ItemCountThresholdProperty); }
            set
            {
                SetValue(ItemCountThresholdProperty, value);
            }
        }

        public static readonly DependencyProperty ControlBackgroundProperty =
            DependencyProperty.Register("ControlBackground", typeof(Brush), typeof(DropDownControl), new PropertyMetadata(null, ControlBackgroundChangedCallback));

        private static void ControlBackgroundChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (DropDownControl)dependencyObject;
            if (e.NewValue == null)
            {
                instance.ControlBackground = _defaulControlBackground;
            }
            instance.Background = instance.ControlBackground;
            if (instance._isLoadedTemplate)
            {
                instance._tapElementExpander.Background = instance.ControlBackground;
                instance._hostPresenter.Background = instance.ControlBackground;
            }
        }

        public Brush ControlBackground
        {
            get { return (Brush)GetValue(ControlBackgroundProperty); }
            set
            {
                SetValue(ControlBackgroundProperty, value);
                SetValue(BackgroundProperty, value);
            }
        }

        public static readonly DependencyProperty ControlForegroundProperty =
            DependencyProperty.Register("ControlForeground", typeof(Brush), typeof(DropDownControl), new PropertyMetadata(null, ControlForegroundChangedCallback));

        private static void ControlForegroundChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (DropDownControl)dependencyObject;
            if (e.NewValue == null)
            {
                instance.ControlForeground = _defaulControlForeground;
            }
            instance.Foreground = instance.ControlForeground;
            if (instance._isLoadedTemplate)
            {
                instance._seletedContentElement.Foreground = instance.ControlForeground;
            }
        }

        public Brush ControlForeground
        {
            get { return (Brush)GetValue(ControlForegroundProperty); }
            set
            {
                SetValue(ControlForegroundProperty, value);
                SetValue(ForegroundProperty, value);
            }
        }

        /// <summary>
        /// Identifies the SelectedItem DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty SelectedItemProperty =
            DependencyProperty.Register("SelectedItem", typeof(object), typeof(DropDownControl), new PropertyMetadata(null, SelectedItemChangedCallback));

        private static void SelectedItemChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (DropDownControl)dependencyObject;
            if (instance._isLoadedTemplate)
            {
                if (null == instance.Items || instance.Items.Count == 0)
                {
                    instance.SelectedItem = null;
                }
                if (instance.SelectedItem == null)
                {
                    instance.SelectedIndex = -1;
                    return;
                }
                var newIndex = -1;
                if (instance.Items != null)
                    foreach (var it in instance.Items)
                    {
                        var item = it as DropDownItem;

                        if (item != null)
                        {
                            if (item.Content.Equals(e.NewValue))
                            {
                                newIndex = instance.Items.IndexOf(item);
                                break;
                            }
                        }
                        else
                        {
                            if (it.Equals(e.NewValue))
                            {
                                newIndex = instance.Items.IndexOf(e.NewValue);
                                break;
                            }
                        }
                    }
                instance.SelectedIndex = newIndex;
            }
        }

        public object SelectedItem
        {
            get { return GetValue(SelectedItemProperty); }
            set 
            {
                SetValue(SelectedItemProperty, value);
            }
        }

        public static readonly DependencyProperty SelectedIndexProperty =
            DependencyProperty.Register("SelectedIndex", typeof(int), typeof(DropDownControl), new PropertyMetadata(-1, SelectedIndexChangedCallback));

        private static void SelectedIndexChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (DropDownControl)dependencyObject;
            if (instance._isLoadedTemplate)
            {
                if (null == instance.Items || instance.Items.Count == 0 || instance.SelectedIndex < 0)
                {
                    instance.SelectedIndex = -1;
                }
                if (null != instance.Items && instance.Items.Count > 0 && instance.SelectedIndex > instance.Items.Count - 1)
                {
                    // ReSharper disable NotResolvedInText
                    throw new ArgumentOutOfRangeException("e");
                    // ReSharper restore NotResolvedInText
                }
                if (instance.SelectedIndex < 0)
                {
                    instance.SelectedItem = null;
                }
                else
                {
                    if (instance.Items != null)
                    {
                        var item = instance.Items[instance.SelectedIndex] as DropDownItem;
                        instance.SelectedItem = item != null ? item.Content : instance.Items[instance.SelectedIndex];
                    }
                }
                instance.ShowSelectedIndex((int)e.OldValue, (int)e.NewValue);
            }
        }

        public int SelectedIndex
        {
            get { return (int)GetValue(SelectedIndexProperty); }
            set
            {
                var oldvalue = (int)GetValue(SelectedIndexProperty);
                if (oldvalue == value)
                {
                    return;
                }
                SetValue(SelectedIndexProperty, value);
            }
        }
        
        public static readonly DependencyProperty EllipseBrushProperty =
            DependencyProperty.Register("EllipseBrush", typeof(Brush), typeof(DropDownControl), new PropertyMetadata(null, EllipseBrushChangedCallback));

        private static void EllipseBrushChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (DropDownControl)dependencyObject;
            if (e.NewValue == null)
            {
                instance.EllipseBrush = _defaultEllipseBrush;
            }
            if (instance._isLoadedTemplate && instance._ellipse != null)
            {
                instance._ellipse.Fill = instance.EllipseBrush;
            }
        }

        public Brush EllipseBrush
        {
            get { return (Brush)GetValue(EllipseBrushProperty); }
            set
            {
                SetValue(EllipseBrushProperty, value);
            }
        }

        public static readonly DependencyProperty DisabledBrushProperty =
            DependencyProperty.Register("DisabledBrush", typeof(Brush), typeof(DropDownControl), new PropertyMetadata(null, DisabledBrushChangedCallback));

        private static void DisabledBrushChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (DropDownControl)dependencyObject;
            if (e.NewValue == null)
            {
                instance.DisabledBrush = _defaultDisabled;
            }
            if (instance._isLoadedTemplate && !instance.IsEnabled)
            {
                instance._seletedContentElement.Foreground = instance.DisabledBrush;
                instance._ellipse.Fill = instance.DisabledBrush;
            }
        }

        public Brush DisabledBrush
        {
            get { return (Brush)GetValue(DisabledBrushProperty); }
            set
            {
                SetValue(DisabledBrushProperty, value);
            }
        }

        public static readonly DependencyProperty EllipseArrowProperty =
                DependencyProperty.Register("EllipseArrow", typeof(Brush), typeof(DropDownControl), new PropertyMetadata(null, EllipseArrowChangedCallback));

        private static void EllipseArrowChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var instance = (DropDownControl)dependencyObject;
            if (e.NewValue == null)
            {
                instance.EllipseArrow = _defaultEllipseArrow;
            }
            if (instance._isLoadedTemplate && instance._arrowExpander != null)
            {
                instance._arrowExpander.Stroke = instance.EllipseArrow;
            }
        }

        public Brush EllipseArrow
        {
            get { return (Brush)GetValue(EllipseArrowProperty); }
            set
            {
                SetValue(EllipseArrowProperty, value);
            }
        }

        #endregion

        #region Load / template / VisualState / SizeChanged

        public DropDownControl()
        {
            DefaultStyleKey = typeof(DropDownControl);
            SizeChanged += DropDownControlSizeChanged;
            IsEnabledChanged += DropDownControlIsEnabledChanged;
        }

        private void DropDownControlIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            VisualStateManager.GoToState(this, IsEnabled?"Collapsed":"Disabled", false);
            if (_seletedContentElement != null)
            {
                _seletedContentElement.Foreground = IsEnabled ? Foreground : DisabledBrush;
                _ellipse.Fill = IsEnabled ? EllipseBrush : DisabledBrush;
            }
        }

        private void DropDownControlSizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (!double.IsNaN(Height))
            {
                Height = double.NaN;
            }
        }

        private void DoWorkOnRender(object sender, EventArgs args)
        {
            CompositionTarget.Rendering -= DoWorkOnRender;
            if (_heightItem != null && _scrollViewer != null)
            {
                _scrollViewer.Height = (ItemCountThreshold * _heightItem.Value);
                _heightContent = _scrollViewer.Height;
            }
            if (_seletedContentElement != null & ItemTemplate != null)
            {
                _seletedContentElement.ContentTemplate = ItemTemplate;
            }
            if (null == Items || Items.Count == 0)
            {
                SelectedIndex = -1;
                return;
            }
            if (SelectedIndex >= 0 && SelectedItem == null)
            {
                ShowSelectedIndex(-1,SelectedIndex);
                //deffered value for SelectedItem
                var item = Items[SelectedIndex] as DropDownItem;
                SetValue(SelectedItemProperty, item != null ? item.Content : Items[SelectedIndex]);
            }
            else if (SelectedItem != null)
            {
                var newIndex = -1;
                foreach (var item in Items)
                {
                    var it = item as DropDownItem;
                    if (it != null)
                    {
                        if (it.Content.Equals(SelectedItem))
                        {
                            newIndex = Items.IndexOf(it);
                            break;
                        }
                    }
                    else
                    {
                        if (item.Equals(SelectedItem))
                        {
                            newIndex = Items.IndexOf(SelectedItem);
                            break;
                        }
                    }
                }
                SelectedIndex = newIndex;
            }
            else
            {
                SelectedIndex = -1;
            }
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _ellipse = (Ellipse)GetTemplateChild("EllipseExpander");
            _arrowExpander = (Path)GetTemplateChild("ArrowExpander");

            _tapElementExpander = (Grid)GetTemplateChild("AreaHeader");

            _scrollViewer = (ScrollViewer)GetTemplateChild("sroolViewItems");
            _seletedContentElement = (ContentControl)GetTemplateChild("SeletedContent");
            _hostPresenter = (Border)GetTemplateChild("HostItemPresenter");
            _areaEllipse = (Border) GetTemplateChild("AreaEllipse");

            ClipBoundsControl.SetToBounds(_tapElementExpander, true);

            _defaultEllipseBrush = new SolidColorBrush((Color)Application.Current.Resources["PhoneAccentColor"]);
            _defaultEllipseArrow = (Brush)Application.Current.Resources["PhoneForegroundBrush"];
            _defaultBorder = (Brush)Application.Current.Resources["PhoneSubtleBrush"];
            _defaultDisabled = (Brush)Application.Current.Resources["PhoneTextBoxReadOnlyBrush"];
            _defaulControlBackground = (Brush)Application.Current.Resources["PhoneTextBoxBrush"];
            _defaulControlForeground = (Brush)Application.Current.Resources["PhoneTextBoxForegroundBrush"];
            _defaultBorderThickness = new Thickness(1, 1, 1, 1);

            if (ControlBackground == null)
            {
                ControlBackground = _defaulControlBackground;
            }

            if (ControlForeground == null)
            {
                ControlForeground = _defaulControlForeground;
            }

            if (DisabledBrush == null)
            {
                DisabledBrush = _defaultDisabled;
            }

            if (BorderBrush == null)
            {
                BorderBrush = _defaultBorder;
            }
            if (BorderThickness == new Thickness())
            {
                BorderThickness = _defaultBorderThickness;
            }

            if (EllipseArrow == null)
            {
                EllipseArrow = _defaultEllipseArrow;
            }
            if (EllipseBrush == null)
            {
                EllipseBrush = _defaultEllipseBrush;
            }

            if (_tapElementExpander != null)
            {
                _tapElementExpander.Tap += TapElementClick;
                _tapElementExpander.Background = Background;
            }

            if (_hostPresenter != null)
            {
                _hostPresenter.Background = Background;
            }

            if (_seletedContentElement != null)
            {
                _seletedContentElement.Foreground = Foreground;
            }

            if (_ellipse != null)
            {
                _ellipse.Fill = EllipseBrush;
            }
            if (_arrowExpander != null)
            {
                _arrowExpander.Stroke = EllipseArrow;
            }

            // Translate it into view once layout has been updated
            Dispatcher.BeginInvoke(() =>
            {
                VisualStateManager.GoToState(this, IsEnabled ? "Collapsed" : "Disabled", false);
                if (_hostPresenter != null)
                {
                    _hostPresenter.Width = ActualWidth - _areaEllipse.ActualWidth;
                    switch (ContainerAlignment)
                    {
                        case DropDownContainerAlignment.Left:
                            _hostPresenter.HorizontalAlignment = HorizontalAlignment.Left;
                            break;
                        case DropDownContainerAlignment.Right:
                            _hostPresenter.HorizontalAlignment = HorizontalAlignment.Right;
                            break;
                        case DropDownContainerAlignment.Center:
                            _hostPresenter.HorizontalAlignment = HorizontalAlignment.Center;
                            break;
                    }
                }
                if (_seletedContentElement != null)
                {
                    _seletedContentElement.Foreground = IsEnabled ? Foreground : DisabledBrush;
                }
                if (_ellipse != null)
                {
                    _ellipse.Fill = IsEnabled ? EllipseBrush : DisabledBrush;
                }
                _isLoadedTemplate = true;
                CompositionTarget.Rendering += DoWorkOnRender;
            });

        }

        private void ChangeVisualState(bool useTransitions)
        {
            if (!_isLoadedTemplate) return;

            Dispatcher.BeginInvoke(() =>
            {
                if (_isExpanded)
                {
                    _hostPresenter.Opacity = Opacity;
                    CreateStoryboardExpandArea().Begin();
                    VisualStateManager.GoToState(this, "Expanded", true);

                }
                else
                {
                    CreateStoryboardCollapsArea().Begin();
                    VisualStateManager.GoToState(this, "Collapsed", useTransitions);
                }
            });

        }

        #endregion

        #region private methods

        private void ShowSelectedIndex(int oldvalue, int newvalue)
        {
            IList removedItems = new object[0];
            IList addedItems = new object[0];
            if (oldvalue != -1)
            {
                if (ItemContainerGenerator != null)
                {
                    var ctold = ItemContainerGenerator.ContainerFromIndex(oldvalue);
                    var itemold = ctold as DropDownItem;
                    if (itemold != null)
                    {
                        removedItems = new[] { itemold.Content };
                        itemold.IsSelected = false;
                    }
                }
            }
            if (newvalue < 0)
            {
                if (_seletedContentElement != null)
                {
                    _seletedContentElement.ContentTemplate = null;
                    _seletedContentElement.Content = null;
                    OnSelectionChange(new SelectionChangedEventArgs(removedItems, addedItems));
                }
                return;
            }
            if (ItemContainerGenerator != null)
            {
                var ct = ItemContainerGenerator.ContainerFromIndex(newvalue);
                var item = ct as DropDownItem;
                if (item != null)
                {
                    item.IsSelected = true;
                    if (_seletedContentElement != null)
                    {
                        if (SelectedTemplate != null)
                        {
                            _seletedContentElement.ContentTemplate = SelectedTemplate;
                        }
                        else if (ItemTemplate != null)
                        {
                            _seletedContentElement.ContentTemplate = ItemTemplate;
                        }
                        _seletedContentElement.Content = item.Content;
                        addedItems = new[] { item.Content };
                        OnSelectionChange(new SelectionChangedEventArgs(removedItems, addedItems));
                    }
                }
            }
        }

        private void TapElementClick(object sender, RoutedEventArgs e)
        {
            if (null == Items || Items.Count == 0)
            {
                return;
            }
            _isExpanded = !_isExpanded;
            OnChangeExpanded(new DropDownOpenChangeEventArgs(_isExpanded));
            ChangeVisualState(true);
        }

        private Storyboard CreateStoryboardExpandArea()
        {
            var sb = new Storyboard();
            var da = new DoubleAnimation { Duration = new Duration(TimeSpan.FromMilliseconds(300)), From = 0, To = _heightContent };
            Storyboard.SetTarget(da, _hostPresenter);
            Storyboard.SetTargetProperty(da, new PropertyPath("(FrameworkElement.Height)"));
            sb.Children.Add(da);
            return sb;
        }

        private Storyboard CreateStoryboardCollapsArea()
        {
            var sb = new Storyboard();
            var da = new DoubleAnimation { Duration = new Duration(TimeSpan.FromMilliseconds(300)), From = _heightContent, To = 0 };
            Storyboard.SetTarget(da, _hostPresenter);
            Storyboard.SetTargetProperty(da, new PropertyPath("(FrameworkElement.Height)"));
            sb.Children.Add(da);
            return sb;
        }

        #endregion

        #region override Items Container

        /// <summary>
        /// Determines if the specified item is (or is eligible to be) its own item container.
        /// </summary>
        /// <param name="item">The specified item.</param>
        /// <returns>True if the item is its own item container; otherwise, false.</returns>
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is DropDownItem;
        }

        /// <summary>
        /// Creates or identifies the element used to display a specified item.
        /// </summary>
        /// <returns>A container corresponding to a specified item.</returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            return new DropDownItem();
        }

        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            base.ClearContainerForItemOverride(element, item);
            // Unhook from events
            var selitem = element as DropDownItem;
            if (selitem != null)
            {
                selitem.Tap -= SelitemTap;
            }
        }
       
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);
            var selitem = element as DropDownItem;
            if (selitem != null)
            {
                selitem.Tap += SelitemTap;
                selitem.SizeChanged += SelitemSizeChanged;
            }
        }

        private void SelitemSizeChanged(object sender, SizeChangedEventArgs e)
        {
            var item = (DropDownItem) sender;
            if (!_heightItem.HasValue)
            {
                _heightItem = item.ActualHeight;
            }
            else
            {
                if (_heightItem < item.ActualHeight)
                {
                    _heightItem = item.ActualHeight;
                }
            }
        }

        private void SelitemTap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            e.Handled = true;
            var item = sender as DropDownItem;
            if (item != null)
            {
                SelectedItem = item.Content;
                _isExpanded = !_isExpanded;
                OnChangeExpanded(new DropDownOpenChangeEventArgs(_isExpanded));
                ChangeVisualState(true);
            }
        }

        #endregion

    }
}
